/*
 * Copyright (c) gitee.com/jiandong001.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import hilog from '@ohos.hilog';
import webSocket from '@ohos.net.webSocket';

export interface MessageCallback {
  onMessage(text: string | ArrayBuffer ): void;
//  void onMessage(ByteString bytes);
}

export abstract class ConnectionCallback {
  abstract onConnected(): void;
  abstract onDisconnected(): void;
}

/** A wrapper around WebSocketClient that reconnects automatically */
export  class ReconnectingWebSocket {
  private static readonly TAG = "React ReconnectingWebSocket";
  private static readonly RECONNECT_DELAY_MS = 2000;

private readonly mUrl: string;
//private final Handler mHandler;
//private readonly mOkHttpClient: OkHttpClient;
private mClosed: boolean = false;
private mSuppressConnectionErrors: boolean;
private mWebSocket = webSocket.createWebSocket();
private mMessageCallback?: MessageCallback;
private mConnectionCallback?: ConnectionCallback;

public constructor(url:string,  messageCallback?: MessageCallback , connectionCallback?: ConnectionCallback) {
  this.mUrl = url;
  this.mMessageCallback = messageCallback;
  this.mConnectionCallback = connectionCallback;
//  mHandler = new Handler(Looper.getMainLooper());
//  this.mOkHttpClient =  new OkHttpClient.Builder()
//                          .connectTimeout(10, TimeUnit.SECONDS)
//                          .writeTimeout(10, TimeUnit.SECONDS)
//                          .readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
//                          .build();

  hilog.info(0x0000, ReconnectingWebSocket.TAG, "constructor ws url: " + url);

  this.mWebSocket.on('open', (err, value) => {
    hilog.info(0x0000, ReconnectingWebSocket.TAG, "on open, status:" + value['status'] + ", message:" + value['message']);

    if (!err) {
      this.mSuppressConnectionErrors = false;
      if (this.mConnectionCallback != null) {
        this.mConnectionCallback.onConnected();
      }
    }

//    // When receiving the on('open') event, the client can use the send() API to communicate with the server.
//    this.mWebSocket.send("Hello, server!", (err, value) => {
//      if (!err) {
//        hilog.info(0x0000, ReconnectingWebSocket.TAG, "send success");
//      } else {
//        hilog.info(0x0000, ReconnectingWebSocket.TAG, "send fail, err:" + JSON.stringify(err));
//      }
//    });
  });
  this.mWebSocket.on('message', (err, value) => {
    hilog.info(0x0000, ReconnectingWebSocket.TAG, "on message, message:" + value);
    // When receiving the bye message (the actual message name may differ) from the server, the client proactively disconnects from the server.
    if (value === 'bye') {
      this.mWebSocket.close((err, value) => {
        if (!err) {
          this.mWebSocket = null;
          if (!this.mClosed) {
            if (this.mConnectionCallback != null) {
              this.mConnectionCallback.onDisconnected();
            }
            this.reconnect();
          }
        }
      });
    } else {
      if (!err) {
        hilog.info(0x0000, ReconnectingWebSocket.TAG, "receive data success");
        if (this.mMessageCallback != null) {
          this.mMessageCallback.onMessage(value as string | ArrayBuffer );
        }
      }
    }
  });
  this.mWebSocket.on('close', (err, value) => {
    hilog.info(0x0000, ReconnectingWebSocket.TAG, "on close, code is " + value['code'] + ", reason is " + value['reason']);
    if (!err) {
       this.mWebSocket = null;
       if (!this.mClosed) {
         if (this.mConnectionCallback != null) {
           this.mConnectionCallback.onDisconnected();
         }
         this.reconnect();
       }
     }
  });
  this.mWebSocket.on('error', (err) => {
    hilog.info(0x0000, ReconnectingWebSocket.TAG, "on error, error:" + JSON.stringify(err));

    this.abort("Websocket exception");
    if (!this.mClosed) {
      if (this.mConnectionCallback != null) {
        this.mConnectionCallback.onDisconnected();
      }
      this.reconnect();
    }
  });

}

public connect():void | never {

  hilog.info(0x0000, ReconnectingWebSocket.TAG, "connect ws url: " + this.mUrl);

    if (this.mClosed) {
      throw new Error("Can't connect closed client");
    }

//    Request request = new Request.Builder().url(mUrl).build();
//    mOkHttpClient.newWebSocket(request, this);

    this.mWebSocket.connect(this.mUrl, (err, value) => {
    if (!err) {
        hilog.info(0x0000, ReconnectingWebSocket.TAG, "connect success");
    } else {
        hilog.info(0x0000, ReconnectingWebSocket.TAG, "connect fail, err:" + JSON.stringify(err))
    }
});



  }
//synchronized
private delayedReconnect(): void {
  // check that we haven't been closed in the meantime
  if (!this.mClosed) {
    this.connect();
  }
}

private reconnect(): void {
    if (this.mClosed) {
      throw new Error("Can't reconnect closed client");
    }

    if (!this.mSuppressConnectionErrors) {
      hilog.warn(0x0000, ReconnectingWebSocket.TAG, "Couldn't connect to  %{public}s will silently retry", this.mUrl);
      this.mSuppressConnectionErrors = true;
    }

    setTimeout(()=> this.delayedReconnect, ReconnectingWebSocket.RECONNECT_DELAY_MS)
  }

public closeQuietly(): void {
  this.mClosed = true;
    this.closeWebSocketQuietly();
    this.mMessageCallback = null;

  if (this.mConnectionCallback != null) {
    this.mConnectionCallback.onDisconnected();
  }
}

private closeWebSocketQuietly() {
  if (this.mWebSocket != null) {
    try {
//      this.mWebSocket.close(1000, "End of session");
      this.mWebSocket.close((err, value) => {
        if (!err) {
          hilog.info(0x0000, ReconnectingWebSocket.TAG, "close success, End of session")
        } else {
          hilog.info(0x0000, ReconnectingWebSocket.TAG, "close fail, err is " + JSON.stringify(err))
        }
      });
    } catch (e) {
    // swallow, no need to handle it here
  }
    this.mWebSocket = null;
}
}

private abort(message: string): void {
  hilog.error(0x0, ReconnectingWebSocket.TAG, "Error occurred, shutting down websocket connection: ");
  this.closeWebSocketQuietly();
}
//
////@Override
////synchronized
//public onOpen(WebSocket webSocket, Response response): void {
//  mWebSocket = webSocket;
//  this.mSuppressConnectionErrors = false;
//
//  if (this.mConnectionCallback != null) {
//  this.mConnectionCallback.onConnected();
//  }
//}
//
////@Override
////synchronized
//public onFailure(WebSocket webSocket, Throwable t, Response response): void {
//  if (mWebSocket != null) {
//    abort("Websocket exception", t);
//  }
//  if (!mClosed) {
//    if (mConnectionCallback != null) {
//      mConnectionCallback.onDisconnected();
//    }
//    reconnect();
//  }
//}
//
////@Override
////synchronized
//public onMessage(WebSocket webSocket, String text): void {
//  if (mMessageCallback != null) {
//    mMessageCallback.onMessage(text);
//  }
//}
//
////@Override
////synchronized
////public onMessage(WebSocket webSocket, ByteString bytes): void {
////  if (mMessageCallback != null) {
////    mMessageCallback.onMessage(bytes);
////  }
////}
//
////@Override
////synchronized
//public onClosed(WebSocket webSocket, int code, String reason): void {
//  this.mWebSocket = null;
//  if (!this.mClosed) {
//    if (this.mConnectionCallback != null) {
//      this.mConnectionCallback.onDisconnected();
//    }
//    reconnect();
//  }
//}

//synchronized
public sendMessage(message: string | ArrayBuffer ) : void | never {
  if (this.mWebSocket != null) {
    this.mWebSocket.send(message);
  } else {
    throw new Error("sendMessage with websocket error: ClosedChannelException");
  }
}
//synchronized
//public sendMessage(ByteString message): void |never {
//  if (mWebSocket != null) {
//    mWebSocket.send(message);
//  } else {
//    throw new ClosedChannelException();
//  }
//}
}
